unit uFrmCity;

interface

uses
  SysUtils, Classes, Windows, Messages, Variants, Graphics, TypInfo, ImgList,
  Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls,
  DLXButtons, uDZTreeView, uGxtCity, uCxtForm;

type
  TAreaSelectOption = (
    asoAllowProvince,
    asoAllowCity,
    asoIncludeProvinceIDOfCity,
    asoIncludeProvinceNameOfCity,
    asoCombineProvinceCityID);

  TAreaSelectOptions = set of TAreaSelectOption;
  
  TFrmCity = class(TBaseForm)
    pnlAction: TPanel;
    Label1: TLabel;
    btnCancel: TButton;
    btnOK: TButton;
    edtSelectedCity: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure btnOKClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    tvwCity: TCheckTreeView;
    fOption: TAreaSelectOptions;
    fSelectd: TStrings;
    procedure tvwCityClickCheckBox(Sender: TObject; node: ComCtrls.TTreeNode);
    procedure SetOption(const Value: TAreaSelectOptions);
    function GetSelected: string;
    procedure SetSelected(const Value: string);
    procedure IncludeCities(pnode: ComCtrls.TTreeNode);
    procedure ExcludeCities(pnode: ComCtrls.TTreeNode);
  public
    { Public declarations }
    procedure GetSelectedAreas(areas: TList);
    procedure GetSelectedAreaNamesAndCodes(var names, codes: string);
    property Option: TAreaSelectOptions read fOption write SetOption;
    property Selected: string read GetSelected write SetSelected;
  end;

function PromptCities(const source: string; areas: TList;
  opt: TAreaSelectOptions): Boolean; overload;
function PromptCities(const source: string; var names: string;
  var codes: string; opt: TAreaSelectOptions): Boolean; overload;
procedure AddProvinceCityHierarchy(Tree: TCheckTreeView; Root: TTreeNode);

implementation

uses uAppConfig;

{$r *.dfm}

var
  FrmCity: TFrmCity;

procedure DeleteStrItem(strs: TStrings; const str: string);
var
  index: Integer;
begin
  index := strs.IndexOf(str);
  if (index >= 0) then strs.Delete(index);
end;

procedure CheckTreeNodeInStrings(tvw: TCheckTreeView; strs: TStrings);
var
  i: Integer;
  node: ComCtrls.TTreeNode;
begin
  for i := 0 to tvw.Items.Count - 1 do
  begin
    node := tvw.Items[i];
    tvw.Checked[node] :=  strs.IndexOf(node.Text) >= 0;
  end;
end;

function PromptCities(const source: string; areas: TList;
  opt: TAreaSelectOptions): Boolean;
begin
  if (FrmCity = nil) then FrmCity := TFrmCity.Create(Application);
  FrmCity.Option := opt;
  FrmCity.Selected := source;
  if (FrmCity.ShowModal = mrOk) then
  begin
    FrmCity.GetSelectedAreas(areas);
    Result := True;
  end
  else Result := False;
end;

function PromptCities(const source: string; var names: string;
  var codes: string; opt: TAreaSelectOptions): Boolean; overload;
begin
  if not Assigned(FrmCity) then FrmCity := TFrmCity.Create(Application);
  FrmCity.Option := opt;
  FrmCity.Selected := source;
  if FrmCity.ShowModal = mrOk then
  begin
    FrmCity.GetSelectedAreaNamesAndCodes(names, codes);
    Result := True;
  end
  else Result := False;
end;

procedure AddProvinceCityHierarchy(Tree: TCheckTreeView; Root: TTreeNode);
var
  i, j: Integer;
  Node: TTreeNode;
  List: TProvinceList;
  Province: TProvince;
  City: TCity;
begin
  List := uAppConfig.AreaList;
  for i := 0 to List.Count - 1 do
  begin
    Province := List.Items[i];
    Node := Tree.Items.AddChildObject(Root, Province.Name, Pointer(Province));
    if (Province.CityCount > 1) or not
      WideSameText(Province.Name, Province.City[0].Name) then
    begin
      for j := 0 to Province.CityCount - 1 do
      begin
        City := Province.City[j];
        Tree.Items.AddChildObject(Node, City.Name, Pointer(City));
      end;
    end;
  end;
end;

{ TFrmCity }

procedure TFrmCity.IncludeCities(pnode: ComCtrls.TTreeNode);
var
  pro: TProvince;
  i: Integer;
begin
  pro := TProvince(pnode.Data);
  for i := 0 to pro.CityCount - 1 do
    fSelectd.Add(pro.City[i].Name);
end;

procedure TFrmCity.btnOKClick(Sender: TObject);
begin
  ModalResult := mrOk;
end;

procedure TFrmCity.ExcludeCities(pnode: ComCtrls.TTreeNode);
var
  child: ComCtrls.TTreeNode;
begin
  child := pnode.getFirstChild;
  while child <> nil do
  begin
    DeleteStrItem(fSelectd, child.Text);
    child := child.getNextSibling;
  end;
end;

procedure TFrmCity.FormCreate(Sender: TObject);
begin
  tvwCity := TCheckTreeView.Create(Self);
  tvwCity.ReadOnly := True;
  tvwCity.OnClickCheckBox := tvwCityClickCheckBox;
  tvwCity.Parent := Self;
  tvwCity.Align := alClient;
  tvwCity.CheckBox := True;
  tvwCity.Show;
  AddProvinceCityHierarchy(tvwCity, nil);
  fSelectd := TStringList.Create;
end;

procedure TFrmCity.FormDestroy(Sender: TObject);
begin
  fSelectd.Free;
end;

function TFrmCity.GetSelected: string;
begin
  Result := edtSelectedCity.Text;
end;

procedure TFrmCity.GetSelectedAreaNamesAndCodes(var names, codes: string);
var
  sc: Integer;
  pnode, cnode: ComCtrls.TTreeNode;
  procedure AddProvince(Province: TProvince; flag: Boolean);
  var
    added: string;
  begin
    if (asoCombineProvinceCityID in Option) then
      added := Format('%.2d00000', [Province.ID])
    else
      added := Format('%.2d', [Province.ID]);

    if (((asoAllowProvince in Option) and flag) or (
      (asoIncludeProvinceNameOfCity in Option) and
      (not flag))) then
    begin
      if (names = '') then names := Province.Name
      else names := names + ',' + Province.Name;
    end;

    if (((asoAllowProvince in Option) and flag) or (
      (asoIncludeProvinceIDOfCity in Option) and
      (not flag))) then
    begin
      if (codes = '') then codes := added
      else codes := codes + ',' + added;
    end;
  end;

  procedure AddCity(City: TCity);
  var
    added: string;
  begin
    if (asoCombineProvinceCityID in fOption) then
      added := Format('%.2d%.5d', [City.Province.ID, City.ID])
    else
      added := Format('%.5d', [City.ID]);
    if (names = '') then
    begin
      names := City.Name;
      codes := added;
    end
    else begin
      names := names + ',' + City.Name;
      codes := codes + ',' + added;
    end;
  end;
begin
  names := '';
  codes := '';
  pnode := tvwCity.Items[0];
  while (pnode <> nil) do
  begin
    if (tvwCity.Checked[pnode]) then
    begin
      if (asoAllowProvince in Option) then
      begin
        AddProvince(TProvince(pnode.Data), True);
        pnode := pnode.getNextSibling;
        Continue;
      end;
    end;
    sc := 0;
    with TProvince(pnode.Data) do
    if (CityCount = 1) then
    begin
      if (tvwCity.Checked[pnode]) then
      begin
        AddCity(City[0]);
        Inc(sc);
      end;
    end
    else begin
      cnode := pnode.getFirstChild;
      while (cnode <> nil) do
      begin
        if (tvwCity.Checked[cnode]) then
        begin
          AddCity(TCity(cnode.Data));
          Inc(sc);
        end;
        cnode := cnode.getNextSibling;
      end;
    end;
    if ((sc > 0) and ((asoIncludeProvinceIDOfCity in fOption) or
      (asoIncludeProvinceNameOfCity in fOption))) then
      AddProvince(TProvince(pnode.Data), False);
    pnode := pnode.getNextSibling;
  end;
end;

procedure TFrmCity.GetSelectedAreas(areas: TList);
var
  pnode, cnode: ComCtrls.TTreeNode;
begin
  areas.Clear;
  pnode := tvwCity.Items[0];
  while (pnode <> nil) do
  begin
    if (tvwCity.Checked[pnode]) then areas.Add(pnode.Data)
    else begin
      cnode := pnode.getFirstChild;
      while (cnode <> nil) do
      begin
        if (tvwCity.Checked[cnode]) then areas.Add(cnode.Data);
        cnode := cnode.getNextSibling;
      end;
    end;
    pnode := pnode.getNextSibling;
  end;
end;

procedure TFrmCity.SetOption(const Value: TAreaSelectOptions);
begin
  fOption := Value;
end;

procedure TFrmCity.SetSelected(const Value: string);
begin
  edtSelectedCity.Text := Value;
  fSelectd.Delimiter := ',';
  fSelectd.DelimitedText := Value;
  CheckTreeNodeInStrings(tvwCity, fSelectd);
end;

procedure TFrmCity.tvwCityClickCheckBox(
  Sender: TObject; node: ComCtrls.TTreeNode);
begin
  if (node.Level = 0) then
  begin
    if (asoAllowProvince in Option) then
    begin
      if (asoAllowCity in Option) then
        tvwCity.CheckChildren(node, tvwCity.Checked[node]);
      if (tvwCity.Checked[node]) then
      begin
        if (asoAllowCity in Option) then
          ExcludeCities(node);
        fSelectd.Add(node.Text);
      end
      else DeleteStrItem(fSelectd, node.Text);
      edtSelectedCity.Text := fSelectd.DelimitedText;
    end
    else begin
      tvwCity.CheckChildren(node, tvwCity.Checked[node]);
      if (tvwCity.Checked[node]) then IncludeCities(node)
      else ExcludeCities(node);
      edtSelectedCity.Text := fSelectd.DelimitedText;
    end;
  end
  else begin
    if (tvwCity.Checked[node]) then
    begin
      if (asoAllowCity in Option) then
      begin
        if (tvwCity.FullChecked(node.Parent)) then
        begin
          tvwCity.Checked[node.Parent] := True;
          if (asoAllowProvince in Option) then
          begin
            ExcludeCities(node.Parent);
            fSelectd.Add(node.Parent.Text);
          end
          else fSelectd.Add(node.Text);
        end
        else fSelectd.Add(node.Text);
        edtSelectedCity.Text := fSelectd.DelimitedText;
      end
      else tvwCity.Checked[node] := False;
    end
    else begin
      if (asoAllowProvince in Option) then
      begin
        if (tvwCity.Checked[node.Parent]) then
        begin
          DeleteStrItem(fSelectd, node.Parent.Text);
          IncludeCities(node.Parent);
        end;
      end
      else 
        DeleteStrItem(fSelectd, node.Text);
      tvwCity.Checked[node.Parent] := False;
      edtSelectedCity.Text := fSelectd.DelimitedText;
    end;
  end;
end;

end.
